/*This section covers some of the methods that can be used to create geometric primitives. There are several ways to deal with geometry objects: at the lowest level loosly wraps OpenGL primitives; an intermediate level using open scene graph basic shapes and at a higher level, loaded from files. This tutorial covers the lowest level. This method provides the greatest flexibility and requies the most effort. Normally at the scene graph level geometry is loaded from files. Most of the effort of tracking vertices is handled by file loaders.
 */

#include <osg/Node>
#include <osg/Group>
#include <osg/Geode>
//The geode class is derived from the 'node' class. Nodes (and thus geodes) can be added to a scene graph as leaf nodes. Geode instances can have any number of 'drawables' associated with them.
#include <osg/Geometry>
//This class loosely wraps the OpenGL drawing primitives - POINTS, LINES, LINE_STRIP, LINE_LOOP,... QUADS,... POLYGON.

#include <osg/Texture2D>
#include <osgDB/ReadFile> 
#include <osgViewer/Viewer>
#include <osg/ShapeDrawable>
#include <osg/PositionAttitudeTransform>
#include <osgGA/TrackballManipulator>
#include <osgGA/FlightManipulator>
#include <osgGA/StateSetManipulator>
#include <osgViewer/ViewerEventHandlers>
#include <osg/LineWidth>

osg::Group* renderWorldReference()
{
    float fCylinderRadius = .1f;
    float fCylinderLength = 10.f;
    float fConeRadius = .4f;
    float fConeLength = 2.f;
    float fOrigRadius = .2f;
    float fLineLength = (fConeLength + fCylinderLength);
//learn how to render point, line, and others
 // a sphere denotes the origin geode
    osg::ref_ptr<osg::ShapeDrawable> pSphere = new osg::ShapeDrawable;
    pSphere->setShape( new osg::Sphere(osg::Vec3(0.0f, 0.0f, 0.0f), fOrigRadius) );
    pSphere->setColor( osg::Vec4(0.0f, 0.0f, 0.0f, 1.0f) );

    osg::Geode* pOriginGeode = new osg::Geode();
    pOriginGeode->addDrawable(pSphere);
 // x-axis red geode
    osg::ref_ptr<osg::ShapeDrawable> pXAxis = new osg::ShapeDrawable;
    pXAxis->setShape( new osg::Cylinder(osg::Vec3(0.0f, 0.0f, 0.0f), fCylinderRadius,fCylinderLength) ); //center of the cylinder, r, height
    pXAxis->setColor( osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) );

    osg::Geode* pXAxisGeode = new osg::Geode();
    pXAxisGeode->addDrawable(pXAxis);

    // Declare and initialize a transform node.
    osg::PositionAttitudeTransform* pR1 = new osg::PositionAttitudeTransform();
    osg::Quat cQ1(0, osg::X_AXIS, M_PI/2.,osg::Y_AXIS, 0,osg::Z_AXIS );
    pR1->setAttitude( cQ1 );
    osg::Vec3 cT1(5.f,.0f,.0f);
    pR1->setPosition( cT1 );

    pR1->addChild(pXAxisGeode);//this should transform the pframidGeode to the next location-shuda
 // x-axis-cone red geode
    osg::ref_ptr<osg::ShapeDrawable> pXCone = new osg::ShapeDrawable;
    pXCone->setShape( new osg::Cone(osg::Vec3(0.0f, 0.0f, 0.0f), fConeRadius,fConeLength) ); //center of the cylinder, r, height
    pXCone->setColor( osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) );

    osg::Geode* pXConeGeode = new osg::Geode();
    pXConeGeode->addDrawable(pXCone);

    // Declare and initialize a transform node.
    osg::PositionAttitudeTransform* pTC1 = new osg::PositionAttitudeTransform();
    osg::Quat cQC1(0, osg::X_AXIS, M_PI/2.,osg::Y_AXIS, 0,osg::Z_AXIS );
    pTC1->setAttitude( cQC1 );
    osg::Vec3 cTC1(10.f,.0f,.0f);
    pTC1->setPosition( cTC1 );

    pTC1->addChild(pXConeGeode);//this should transform the pframidGeode to the next location-shuda

 // y-axis green geode
    osg::ref_ptr<osg::ShapeDrawable> pYAxis = new osg::ShapeDrawable;
    pYAxis->setShape( new osg::Cylinder(osg::Vec3(0.0f, 0.0f, 0.0f), fCylinderRadius,fCylinderLength) ); //center of the cylinder, r, height
    pYAxis->setColor( osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) );

    osg::Geode* pYAxisGeode = new osg::Geode();
    pYAxisGeode->addDrawable(pYAxis);

    // Declare and initialize a transform node.
    osg::PositionAttitudeTransform* pR2 = new osg::PositionAttitudeTransform();
    osg::Quat cQ2(M_PI/2, osg::X_AXIS, 0,osg::Y_AXIS, 0,osg::Z_AXIS );
    pR2->setAttitude( cQ2 );
    osg::Vec3 cT2(.0f,5.0f,.0f);
    pR2->setPosition( cT2 );

    pR2->addChild(pYAxisGeode);//this should transform the pframidGeode to the next location-shuda
 // y-axis-cone green geode
    osg::ref_ptr<osg::ShapeDrawable> pYCone = new osg::ShapeDrawable;
    pYCone->setShape( new osg::Cone(osg::Vec3(0.0f, 0.0f, 0.0f), fConeRadius,fConeLength) ); //center of the cylinder, r, height
    pYCone->setColor( osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) );

    osg::Geode* pYConeGeode = new osg::Geode();
    pYConeGeode->addDrawable(pYCone);

    // Declare and initialize a transform node.
    osg::PositionAttitudeTransform* pTC2 = new osg::PositionAttitudeTransform();
    osg::Quat cQC2(-M_PI/2., osg::X_AXIS, 0,osg::Y_AXIS, 0,osg::Z_AXIS );
    pTC2->setAttitude( cQC2 );
    osg::Vec3 cTC2(.0f,10.0f,.0f);
    pTC2->setPosition( cTC2 );

    pTC2->addChild(pYConeGeode);//this should transform the pframidGeode to the next location-shuda

 // z-axis blue geode
    osg::ref_ptr<osg::ShapeDrawable> pZAxis = new osg::ShapeDrawable;
    pZAxis->setShape( new osg::Cylinder(osg::Vec3(0.0f, 0.0f, 0.0f), fCylinderRadius,fCylinderLength) ); //center of the cylinder, r, height
    pZAxis->setColor( osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) );

    osg::Geode* pZAxisGeode = new osg::Geode();
    pZAxisGeode->addDrawable(pZAxis);

    // Declare and initialize a transform node.
    osg::PositionAttitudeTransform* pR3 = new osg::PositionAttitudeTransform();
    osg::Quat cQ3(0, osg::X_AXIS, 0,osg::Y_AXIS, M_PI/2,osg::Z_AXIS );
    pR3->setAttitude( cQ3 );
    osg::Vec3 cT3(.0f,.0f,5.0f);
    pR3->setPosition( cT3 );

    pR3->addChild(pZAxisGeode);//this should transform the pframidGeode to the next location-shuda
 // z-axis-cone green geode
    osg::ref_ptr<osg::ShapeDrawable> pZCone = new osg::ShapeDrawable;
    pZCone->setShape( new osg::Cone(osg::Vec3(0.0f, 0.0f, 0.0f), fConeRadius,fConeLength) ); //center of the cylinder, r, height
    pZCone->setColor( osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) );

    osg::Geode* pZConeGeode = new osg::Geode();
    pZConeGeode->addDrawable(pZCone);

    // Declare and initialize a transform node.
    osg::PositionAttitudeTransform* pTC3 = new osg::PositionAttitudeTransform();
    osg::Quat cQC3(0, osg::X_AXIS, 0.,osg::Y_AXIS, 0,osg::Z_AXIS );
    pTC3->setAttitude( cQC3 );
    osg::Vec3 cTC3(0.f,.0f,10.0f);
    pTC3->setPosition( cTC3 );

    pTC3->addChild(pZConeGeode);//this should transform the pframidGeode to the next location-shuda


 //line-based axis geode
    osg::Geode* pAxisGeode = new osg::Geode();
    osg::Geometry* pAxisGeometry = new osg::Geometry();
    pAxisGeode->addDrawable(pAxisGeometry);

    osg::Vec3Array* pAxis = new osg::Vec3Array;
    pAxis->push_back( osg::Vec3(  0,0,0 ) );
    pAxis->push_back( osg::Vec3( fLineLength,0,0 ) );
    pAxis->push_back( osg::Vec3( 0,fLineLength,0 ) );   
    pAxis->push_back( osg::Vec3( 0,0,fLineLength ) );

    pAxisGeometry->setVertexArray( pAxis );
    //render x 
    osg::DrawElementsUInt* pAxisX = new osg::DrawElementsUInt(osg::PrimitiveSet::LINES, 0);
    pAxisX->push_back(0);
    pAxisX->push_back(1);
    pAxisGeometry->addPrimitiveSet(pAxisX);
    //render y 
    osg::DrawElementsUInt* pAxisY = new osg::DrawElementsUInt(osg::PrimitiveSet::LINES, 0);
    pAxisY->push_back(0);
    pAxisY->push_back(2);
    pAxisGeometry->addPrimitiveSet(pAxisY);
     //render z 
    osg::DrawElementsUInt* pAxisZ = new osg::DrawElementsUInt(osg::PrimitiveSet::LINES, 0);
    pAxisZ->push_back(0);
    pAxisZ->push_back(3);
    pAxisGeometry->addPrimitiveSet(pAxisZ);
    // set line width
    osg::StateSet* pStateSet = new osg::StateSet;
    osg::LineWidth* pLineWidth = new osg::LineWidth();
    pLineWidth->setWidth(1.0f);
    pStateSet->setAttributeAndModes(pLineWidth,osg::StateAttribute::ON);
    pStateSet->setMode(GL_LIGHTING,osg::StateAttribute::OFF);
    pAxisGeometry->setStateSet(pStateSet);
    //color
    osg::Vec4Array* pAxisColors = new osg::Vec4Array;
    //pAxisColors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f) ); //index 0 white
    pAxisColors->push_back(osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) ); //index 1 red
    pAxisColors->push_back(osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) ); //index 2 green
    pAxisColors->push_back(osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) ); //index 3 blue
    //The next step is to associate the array of colors with the geometry,
    //assign the color indices created above to the geometry and set the
    //binding mode to _PER_VERTEX.
    pAxisGeometry->setColorArray(pAxisColors);
    pAxisGeometry->setColorBinding(osg::Geometry::BIND_PER_PRIMITIVE);

    osg::Group* pRoot = new osg::Group();// a 'group' instance to serve as the root of the scene graph
    pRoot->addChild(pAxisGeode);
    pRoot->addChild(pOriginGeode);
    pRoot->addChild(pR1);
    pRoot->addChild(pTC1);
    pRoot->addChild(pR2);
    pRoot->addChild(pTC2);
    pRoot->addChild(pR3);
    pRoot->addChild(pTC3);

    return pRoot;
}

osg::Geode* renderPyramids()
{
    //Associate the pyramid geometry with the pyramid geode
    //   Add the pyramid geode to the root node of the scene graph.
    osg::Geode* pyramidGeode = new osg::Geode();// a geometry node (geode) to collect drawables
    osg::Geometry* pyramidGeometry = new osg::Geometry();//a geometry instance to associate vertices and vertex data. (In this case the shape to render is a four-sided pyramid.)

    pyramidGeode->addDrawable(pyramidGeometry);

    //Declare an array of vertices. Each vertex will be represented by
    //a triple -- an instances of the vec3 class. An instance of
    //osg::Vec3Array can be used to store these triples. Since
    //osg::Vec3Array is derived from the STL vector class, we can use the
    //push_back method to add array elements. Push back adds elements to
    //the end of the vector, thus the index of first element entered is
    //zero, the second entries index is 1, etc.
    //Using a right-handed coordinate system with 'z' up, array
    //elements zero..four below represent the 5 points required to create
    //a simple pyramid.

    osg::Vec3Array* pyramidVertices = new osg::Vec3Array;
    pyramidVertices->push_back( osg::Vec3( 0, 0, 0) ); // front left
    pyramidVertices->push_back( osg::Vec3(10, 0, 0) ); // front right
    pyramidVertices->push_back( osg::Vec3(10,10, 0) ); // back right
    pyramidVertices->push_back( osg::Vec3( 0,10, 0) ); // back left
    pyramidVertices->push_back( osg::Vec3( 5, 5,10) ); // peak

    //Associate this set of vertices with the geometry associated with the
    //geode we added to the scene.

    pyramidGeometry->setVertexArray( pyramidVertices );

    //Next, create a primitive set and add it to the pyramid geometry.
    //Use the first four points of the pyramid to define the base using an
    //instance of the DrawElementsUint class. Again this class is derived
    //from the STL vector, so the push_back method will add elements in
    //sequential order. To ensure proper backface cullling, vertices
    //should be specified in counterclockwise order. The arguments for the
    //constructor are the enumerated type for the primitive
    //(same as the OpenGL primitive enumerated types), and the index in
    //the vertex array to start from.

    osg::DrawElementsUInt* pyramidBase = new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
    pyramidBase->push_back(3);
    pyramidBase->push_back(2);
    pyramidBase->push_back(1);
    pyramidBase->push_back(0);
    pyramidGeometry->addPrimitiveSet(pyramidBase);

    //Repeat the same for each of the four sides. Again, vertices are
    //specified in counter-clockwise order.

    osg::DrawElementsUInt* pyramidFaceOne = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
    pyramidFaceOne->push_back(0);
    pyramidFaceOne->push_back(1);
    pyramidFaceOne->push_back(4);
    pyramidGeometry->addPrimitiveSet(pyramidFaceOne);

    osg::DrawElementsUInt* pyramidFaceTwo = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
    pyramidFaceTwo->push_back(1);
    pyramidFaceTwo->push_back(2);
    pyramidFaceTwo->push_back(4);
    pyramidGeometry->addPrimitiveSet(pyramidFaceTwo);

    osg::DrawElementsUInt* pyramidFaceThree = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
    pyramidFaceThree->push_back(2);
    pyramidFaceThree->push_back(3);
    pyramidFaceThree->push_back(4);
    pyramidGeometry->addPrimitiveSet(pyramidFaceThree);

    osg::DrawElementsUInt* pyramidFaceFour = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
    pyramidFaceFour->push_back(3);
    pyramidFaceFour->push_back(0);
    pyramidFaceFour->push_back(4);
    pyramidGeometry->addPrimitiveSet(pyramidFaceFour);

    //Declare and load an array of Vec4 elements to store colors.

    osg::Vec4Array* colors = new osg::Vec4Array;
    colors->push_back(osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) ); //index 0 red
    colors->push_back(osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) ); //index 1 green
    colors->push_back(osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) ); //index 2 blue
    colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f) ); //index 3 white
    colors->push_back(osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) ); //index 4 red

    //The next step is to associate the array of colors with the geometry,
    //assign the color indices created above to the geometry and set the
    //binding mode to _PER_VERTEX.

    pyramidGeometry->setColorArray(colors);
    pyramidGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);

    return pyramidGeode;
}

int main()
{
    osg::Group* root = new osg::Group();// a 'group' instance to serve as the root of the scene graph

    //render world reference system
    root->addChild(renderWorldReference());
    //render two pyramids
    osg::Geode* pPyramid = renderPyramids();
    root->addChild(pPyramid);

    //Now that we have created a geometry node and added it to the scene
    //we can reuse this geometry. For example, if we wanted to put a
    //second pyramid 15 units to the right of the first one, we could add
    //this geode as the child of a transform node in our scene graph.

    // Declare and initialize a transform node.
    osg::PositionAttitudeTransform* pyramidTwoXForm = new osg::PositionAttitudeTransform();

    // Use the 'addChild' method of the osg::Group class to
    // add the transform as a child of the root node and the
    // pyramid node as a child of the transform.

    root->addChild(pyramidTwoXForm);
    pyramidTwoXForm->addChild(pPyramid);//this should transform the pframidGeode to the next location-shuda

    // Declare and initialize a Vec3 instance to change the
    // position of the model in the scene

    osg::Vec3 pyramidTwoPosition(15,0,0);
    pyramidTwoXForm->setPosition( pyramidTwoPosition );

    // switch off lighting as we haven't assigned any normals.
    root->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);

    //The final step is to set up and enter a simulation loop.
    osgViewer::Viewer viewer;
    viewer.setSceneData( root );
    //viewer.run();

    viewer.setCameraManipulator(new osgGA::TrackballManipulator());
    
// add the state manipulator
    osg::ref_ptr<osgGA::StateSetManipulator> statesetManipulator = new osgGA::StateSetManipulator;
    statesetManipulator->setStateSet(viewer.getCamera()->getOrCreateStateSet());

    viewer.addEventHandler( statesetManipulator.get() );

    viewer.addEventHandler( new osgViewer::StatsHandler );
    viewer.addEventHandler( new osgViewer::HelpHandler );
    viewer.addEventHandler( new osgViewer::WindowSizeHandler );
    viewer.addEventHandler( new osgViewer::ThreadingHandler );
    viewer.addEventHandler( new osgViewer::RecordCameraPathHandler );

    viewer.realize();

    while( !viewer.done() )
    {
        viewer.frame();
    }

    return 0;
}
